package errcode

import (
	"context"
	"database/sql"
	"net/http"
	"sync"
)

// codes contains a map of error codes to metadata.
var codes = map[error]Coder{}
var codeMux = &sync.Mutex{}

var (
	unknownCoder = defaultCoder{http.StatusInternalServerError, http.StatusInternalServerError, "服务器内部错误", ErrInternal}
)

func init() {
	codes[ErrInternal] = unknownCoder
	register(defaultCoder{codeNoResult, http.StatusBadRequest, "查找失败，无此记录", sql.ErrNoRows})
	register(defaultCoder{codeTimeout, http.StatusInternalServerError, "请求超时", context.DeadlineExceeded})
	register(defaultCoder{codeAuthCheckToken, http.StatusBadRequest, "解析Token失败", ErrAuthCheckToken})
	register(defaultCoder{http.StatusTooManyRequests, http.StatusTooManyRequests, "服务器繁忙", ErrTooManyRequest})
	register(defaultCoder{codeAuthCheckTokenTimeOut, http.StatusBadRequest, "Token过期", ErrAuthCheckTokenTimeOut})
	register(defaultCoder{codeAuth, http.StatusBadRequest, "Token错误", ErrAuth})
	register(defaultCoder{codeParamTypeError, http.StatusBadRequest, "参数错误", ErrParamTypeError})
	register(defaultCoder{codeUsernameExist, http.StatusBadRequest, "用户名已被注册", ErrUsernameHadBeenRegistered})
	register(defaultCoder{codeLoginFail, http.StatusBadRequest, "登录失败", ErrLoginFail})
}

type Coder interface {
	// HTTPStatus  HTTP status that should be used for the associated error code.
	HTTPStatus() int

	// Code returns the code of the coder
	Code() int

	// String External (user) facing error text.
	String() string

	// Error 返回error
	Error() error
}

type defaultCoder struct {
	// C refers to the integer code of the ErrCode.
	C int

	// HTTP status that should be used for the associated error code.
	HTTP int

	// External (user) facing error text.
	Ext string

	Err error
}

// Code returns the integer code of the coder.
func (coder defaultCoder) Code() int {
	return coder.C
}

// String implements stringer. String returns the external error message,
// if any.
func (coder defaultCoder) String() string {
	return coder.Ext
}

// HTTPStatus returns the associated HTTP status code, if any. Otherwise,
// returns 200.
func (coder defaultCoder) HTTPStatus() int {
	if coder.HTTP == 0 {
		return 500
	}

	return coder.HTTP
}

func (coder defaultCoder) Error() error {
	return coder.Err
}

// ParseCoder parse any error into *withCode.
// nil error will return nil direct.
// None withStack error will be parsed as ErrUnknown.
func ParseCoder(err error) Coder {
	if err == nil {
		return nil
	}

	if coder, ok := codes[err]; ok {
		return coder
	}

	return unknownCoder
}

// register register a user define error code.
func register(coder Coder) {
	if coder.Code() == 0 {
		panic("code `0` is reserved as unknownCode error code")
	}

	codeMux.Lock()
	defer codeMux.Unlock()

	codes[coder.Error()] = coder
}
